package indi.yangshenhui.hbase.dao.resolve;

import indi.yangshenhui.hbase.dao.annotation.HBaseColumn;
import indi.yangshenhui.hbase.dao.annotation.HBaseRowkey;
import indi.yangshenhui.hbase.dao.annotation.HBaseTable;
import indi.yangshenhui.hbase.dao.exception.HBaseDaoException;
import indi.yangshenhui.tool.CollectionUtil;
import indi.yangshenhui.tool.ObjectUtil;
import indi.yangshenhui.tool.StringUtil;

import java.lang.reflect.Field;
import java.util.Map;

import com.google.common.collect.Maps;

/**
 * 
 * @author yangshenhui
 */
public class HBaseTableEntityAnnotationManage {
	private static volatile Map<Class<?>, HBaseTableEntityAnnotation> hBaseTableEntityAnnotationCacheMap = Maps
			.newHashMap();

	public static <T> HBaseTableEntityAnnotation getEntityAnnotation(
			Class<T> clazz) throws HBaseDaoException {
		if (ObjectUtil.isNull(clazz)) {
			return null;
		}
		HBaseTableEntityAnnotation hBaseTableEntityAnnotation = hBaseTableEntityAnnotationCacheMap
				.get(clazz);
		if (ObjectUtil.isNotNull(hBaseTableEntityAnnotation)) {
			return hBaseTableEntityAnnotation;
		}
		synchronized (hBaseTableEntityAnnotationCacheMap) {
			HBaseTable hBaseTable = clazz.getAnnotation(HBaseTable.class);
			if (ObjectUtil.isNull(hBaseTable)) {
				throw new HBaseDaoException(clazz.getName()
						+ " is not annotationed by @HBaseTable");
			}
			if (StringUtil.isEmpty(hBaseTable.tableName())) {
				throw new HBaseDaoException(
						clazz.getName()
								+ " is annotationed by @HBaseTable and table name can not be empty");
			}
			hBaseTableEntityAnnotation = new HBaseTableEntityAnnotation();
			hBaseTableEntityAnnotation.setClazz(clazz);
			for (Field field : clazz.getDeclaredFields()) {
				processHBaseRowkey(hBaseTableEntityAnnotation, field);
				processHBaseColumn(hBaseTableEntityAnnotation, field);
			}
			checkAnnotation(clazz, hBaseTableEntityAnnotation);
			hBaseTableEntityAnnotationCacheMap.put(clazz,
					hBaseTableEntityAnnotation);
		}
		return hBaseTableEntityAnnotation;
	}

	private static void processHBaseRowkey(
			HBaseTableEntityAnnotation hBaseTableEntityAnnotation, Field field)
			throws HBaseDaoException {
		if (ObjectUtil.isNull(hBaseTableEntityAnnotation)
				|| ObjectUtil.isNull(field)) {
			return;
		}
		HBaseRowkey hBaseRowkey = field.getAnnotation(HBaseRowkey.class);
		if (ObjectUtil.isNull(hBaseRowkey)) {
			return;
		}
		if (ObjectUtil.isNotNull(hBaseTableEntityAnnotation
				.gethBaseRowkeyField())) {
			throw new HBaseDaoException(
					"@HBaseRowkey can only define on no more than one field");
		}
		hBaseTableEntityAnnotation.sethBaseRowkeyField(field);
	}

	private static void processHBaseColumn(
			HBaseTableEntityAnnotation hBaseTableEntityAnnotation, Field field)
			throws HBaseDaoException {
		if (ObjectUtil.isNull(hBaseTableEntityAnnotation)
				|| ObjectUtil.isNull(field)) {
			return;
		}
		HBaseColumn hBaseColumn = field.getAnnotation(HBaseColumn.class);
		if (ObjectUtil.isNull(hBaseColumn)) {
			return;
		}
		HBaseTable hBaseTable = hBaseTableEntityAnnotation.getClazz()
				.getAnnotation(HBaseTable.class);
		checkFamily(field.getName(), hBaseColumn.family(), hBaseTable.family());
		hBaseTableEntityAnnotation.addHBaseColumn(field);
	}

	private static void checkFamily(String fieldName, String family,
			String defaultFamily) throws HBaseDaoException {
		if (StringUtil.isEmpty(family) && StringUtil.isEmpty(defaultFamily)) {
			throw new HBaseDaoException(fieldName + " does not define a family");
		}
	}

	private static <T> void checkAnnotation(Class<T> clazz,
			HBaseTableEntityAnnotation hBaseTableEntityAnnotation)
			throws HBaseDaoException {
		if (ObjectUtil.isNull(clazz)
				|| ObjectUtil.isNull(hBaseTableEntityAnnotation)) {
			return;
		}
		if (ObjectUtil.isNull(hBaseTableEntityAnnotation.gethBaseRowkeyField())) {
			throw new HBaseDaoException(clazz.getName()
					+ " does not define a @HBaseTable field");
		}
		if (CollectionUtil.isEmpty(hBaseTableEntityAnnotation
				.gethBaseColumnFieldList())) {
			throw new HBaseDaoException(clazz.getName()
					+ " should define at least one @HBaseColumn filed");
		}
	}

	private HBaseTableEntityAnnotationManage() {
	}

}